package Logic;

import java.util.Random;

import Data.GameData;
import Data.GameException;
import Data.GameInfo;
import Data.Player;
import Data.Character;
import Logic.Board.CellStatus;

public class Game 
{
	private Board board;
	private Player player1;
	private Player player2;
	
	private GameData gameData;
	private GameInfo latestGameInfo;
	
	private int gameId;
	private Random rand;
	
	private CurrentPlayerStatus currentPlayerStatus;
	
	/**
	 * Represents possible game statuses
	 */
	public enum GameOverStatus
	{
		NotOver,
		Player1,
		Player2,
		Tie
	}
	
	/**
	 * Represents current active player (who's turn it is)
	 */
	public enum CurrentPlayerStatus
	{
		Player1,
		Player2
	}
	
	public Game()
	{
		board = new Board();
		player1 = new Player();
		player2 = new Player();
		
		gameData = new GameData();
		
		rand = new Random();
		
		currentPlayerStatus = CurrentPlayerStatus.Player1;
	}
	
	/**
	 * @return game board
	 */
	public Board getBoard()
	{
		return board;
	}
	
	/**
	 * @return player 1 in game
	 */
	public Player getPlayer1()
	{
		return player1;
	}
	
	/**
	 * @return player 2 in game
	 */
	public Player getPlayer2()
	{
		return player2;
	}
	
	/**
	 * Start game and initialize board.
	 * Should be called after initializing players.
	 * @throws GameException
	 */
	public void startGame() throws GameException
	{
		gameId = gameData.startNewGame(player1, player2);
		
		board.initialize(Settings.getBoardSize());
		saveState();
	}
	
	/**
	 * Save game state to database
	 * @throws GameException
	 */
	public void saveState() throws GameException
	{
		byte[] boardState = new byte[Settings.getBoardSize() * Settings.getBoardSize() + 1];
		
		// save board cells
		for (int i = 0; i < Settings.getBoardSize() * Settings.getBoardSize(); i++)
		{
			int x = i % Settings.getBoardSize();
			int y = i / Settings.getBoardSize();
			
			boardState[i] = (byte)board.getCellStatus(x, y).getVal();
		}
		
		boardState[boardState.length - 1] = (byte)(currentPlayerStatus == CurrentPlayerStatus.Player1 ? 0 : 1);
		
		gameData.saveState(gameId, player1, player2, boardState);
	}
	
	/**
	 * Retrieve the latest game's information
	 * @return GameInfo object containing the information
	 * @throws GameException
	 */
	public GameInfo getLatestGame() throws GameException
	{
		latestGameInfo = gameData.getLatestGame();
		return latestGameInfo;
	}
	
	/**
	 * Resumes latest game played
	 * @return if game to resume has been found
	 * @throws GameException
	 */
	public boolean resumeLatestGame() throws GameException 
	{
		if (latestGameInfo == null)
			getLatestGame();
		
		gameId = latestGameInfo.getId();
		
		board.initialize(Settings.getBoardSize());
		
		byte[] boardState = new byte[Settings.getBoardSize() * Settings.getBoardSize() + 1];
		
		if (!gameData.loadState(latestGameInfo.getStateId(), player1, player2, boardState))
			return false;
		
		// update board cells
		for (int i = 0; i < Settings.getBoardSize() * Settings.getBoardSize(); i++)
		{
			int x = i % Settings.getBoardSize();
			int y = i / Settings.getBoardSize();
			
			board.setCellStatus(x, y, CellStatus.getByInt(boardState[i]));
		}
		
		currentPlayerStatus = (boardState[boardState.length - 1] == 0) ?
				CurrentPlayerStatus.Player1 :
				CurrentPlayerStatus.Player2;
		
		return true;
	}
	
	/**
	 * Simulate fight between two characters
	 * @param attackingPlayer : the player that initiated the fight
	 * @param attackingChar   : the character which will participate in the fight
	 * @param defendingPlayer : the defending player
	 * @param defendingChar   : the character chosen to defend the player
	 * @param srcX		      : x-coord of attacker's origin
	 * @param srcY			  : y-coord of attacker's origin
	 * @param dstX			  : x-coord of cell attacked (under the defender's control)
	 * @param dstY			  : y-coord of cell attacked
	 */
	public void fight(Player attackingPlayer, 
					  Character attackingChar,
					  Player defendingPlayer, 
					  Character defendingChar,
					  int srcX, 
					  int srcY, 
					  int dstX, 
					  int dstY)
	{	
		int attackScore = attackingChar.getHealth();
		int defendScore = defendingChar.getHealth();
	
		int dist = Math.abs(srcX - dstX) + Math.abs(srcY - dstY);
		
		attackScore -= dist;
		
		if (attackingChar.getWeight() > defendingChar.getWeight())
		{
			attackScore += 10;
		}
		else if (attackingChar.getWeight() < defendingChar.getWeight())
		{
			defendScore += 10;
		}
		
		if (attackingChar.getMedicalCondition().size() > 0)
		{
			attackScore -= 15;
		}
		
		if (defendingChar.getMedicalCondition().size() > 0)
		{
			defendScore -= 15;
		}
		
		// add some randomness
		attackScore += rand.nextInt(10);
		defendScore += rand.nextInt(10);
		
		float attackRatio = attackScore / (float)defendScore;
		Data.Character winningChar = attackRatio >= 1 ? attackingChar : defendingChar;
		Data.Character losingChar = attackRatio >= 1 ? defendingChar : attackingChar;
		attackRatio = attackRatio > 1 ? (1 / attackRatio) : attackRatio;
		
		int healthDecrease = (int)(winningChar.getHealth() / attackRatio);
		winningChar.setHealth(winningChar.getHealth() - healthDecrease/4);
		losingChar.setHealth(losingChar.getHealth() - healthDecrease/2);

		if (winningChar.getHealth() <= 0)
		{
			winningChar.setHealth(0);
			winningChar.setIsDead(true);
		}
		
		if (losingChar.getHealth() <= 0)
		{
			losingChar.setHealth(0);
			losingChar.setIsDead(true);
		}
		
		if (defendingChar.isDead() && attackingChar.isDead())
		{
			board.setCellStatus(dstX, dstY, CellStatus.Empty);
		}
		else if (winningChar == attackingChar)
		{
			board.setCellStatus(dstX, dstY, attackingPlayer == player1 ? CellStatus.Player1 : CellStatus.Player2);
		}
	}
	
	/**
	 * @return game status - not finished, finished with each possible outcome
	 */
	public GameOverStatus getGameOverStatus()
	{
		boolean player1Dead = true;
		boolean player2Dead = true;
		
		for (int i = 0; i < Settings.getArmySize(); i++)
		{
			player1Dead = player1Dead && player1.getArmy()[i].isDead();
			player2Dead = player2Dead && player2.getArmy()[i].isDead();
		}
		
		if (player1Dead && player2Dead)
			return GameOverStatus.Tie;
		else if (player1Dead)
			return GameOverStatus.Player2;
		
		if (!player1Dead && !player2Dead)
		{
			CellStatus status = CellStatus.Empty;
			
			// check if entire board has been covered by single player
			for (int x = 0; x < board.getSize(); x++)
			{
				for (int y = 0; y < board.getSize(); y++)
				{
					CellStatus currentStatus = board.getCellStatus(x, y);
					
					// haven't encountered a player's cell yet
					if (status == CellStatus.Empty)
						status = currentStatus;
					
					// if first cell different than current (ignoring empty)
					if (currentStatus != CellStatus.Empty &&
						status != currentStatus)
					{
						return GameOverStatus.NotOver;
					}
				}
			}
			
			return (status == CellStatus.Player1) ? GameOverStatus.Player1 
												  : GameOverStatus.Player2;
		}
		
		return GameOverStatus.Player1;
	}
	
	/**
	 * @return current player's turn
	 */
	public CurrentPlayerStatus getCurrentPlayerStatus()
	{
		return currentPlayerStatus;
	}
	
	/**
	 * Set active player in game
	 * @param currentPlayer: the player who is active
	 */
	public void setCurrentPlayerStatus(CurrentPlayerStatus currentPlayer)
	{
		this.currentPlayerStatus = currentPlayer;
	}
	
	/**
	 * @return actual active player
	 */
	public Player getCurrentPlayer()
	{
		return (currentPlayerStatus == CurrentPlayerStatus.Player1) ? player1 : player2;
	}
	
	/**
	 * @return idle player in game
	 */
	public Player getIdlePlayer()
	{
		return (currentPlayerStatus == CurrentPlayerStatus.Player1) ? player2 : player1;
	}
	
	/**
	 * Switch current active and idle players
	 */
	public void switchCurrentPlayer()
	{
		currentPlayerStatus = (currentPlayerStatus == CurrentPlayerStatus.Player1) ?
				CurrentPlayerStatus.Player2 :
				CurrentPlayerStatus.Player1;
	}
}
